/**
 * OSAmI: Open Source Ambient Intelligence
 * Copyright (C) 2010 Bull S.A.S.
 * Contact: osami-general@osami-commons.org
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 *
 * --------------------------------------------------------------------------
 * $Id: SecureHttpContext.java 337 2010-12-02 17:55:14Z guillaume.sauthier $
 * --------------------------------------------------------------------------
 */


package org.osami.gateway.display.internal.security;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URL;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.felix.ipojo.annotations.Component;
import org.apache.felix.ipojo.annotations.Instantiate;
import org.apache.felix.ipojo.annotations.Property;
import org.apache.felix.ipojo.annotations.Provides;
import org.apache.felix.ipojo.annotations.Requires;
import org.apache.felix.ipojo.annotations.Validate;
import org.osgi.service.http.HttpContext;
import org.osgi.service.http.HttpService;

/**
 * A {@code SecureHttpContext} is ... shamelessly borrowed from Felix Web Console.
 * http://svn.apache.org/viewvc/felix/trunk/webconsole/src/main/java/org/apache/felix/webconsole/internal/servlet/OsgiManagerHttpContext.java?view=markup
 */
@Component
@Provides
@Instantiate
public class SecureHttpContext implements HttpContext {

    private static final String HEADER_WWW_AUTHENTICATE = "WWW-Authenticate";

    private static final String HEADER_AUTHORIZATION = "Authorization";

    private static final String AUTHENTICATION_SCHEME_BASIC = "Basic";

    @Property(value = "OSAmI Web Console",
              mandatory = true)
    private String realm;

    @Requires(optional = true,
              nullable = false)
    private SecurityProvider securityProvider;

    @Requires
    private HttpService httpService;

    private HttpContext delegate;


    @Validate
    private void start() {
        this.delegate = this.httpService.createDefaultHttpContext();
    }


    public String getMimeType(String name) {
        return this.delegate.getMimeType(name);
    }


    public URL getResource(String name) {
        URL url = this.delegate.getResource(name);
        if (url == null && name.endsWith("/")) {
            return this.delegate.getResource(name.substring(0, name.length() - 1));
        }
        return url;
    }


    /**
     * Checks the <code>Authorization</code> header of the request for Basic
     * authentication user name and password. If contained, the credentials are
     * compared to the user name and password set for the OSGi Console.
     * <p/>
     * If no user name is set, the <code>Authorization</code> header is
     * ignored and the client is assumed to be authenticated.
     *
     * @param request  The HTTP request used to get the
     *                 <code>Authorization</code> header.
     * @param response The HTTP response used to send the authentication request
     *                 if authentication is required but not satisfied.
     * @return <code>true</code> if authentication is required and not
     *         satisfied by the request.
     */
    public boolean handleSecurity(HttpServletRequest request, HttpServletResponse response) {

        // don't care for authentication if no user name is configured
        if (this.securityProvider == null) {
            return true;
        }

        // Return immediately if the header is missing
        String authHeader = request.getHeader(HEADER_AUTHORIZATION);
        if (authHeader != null && authHeader.length() > 0) {

            // Get the authType (Basic, Digest) and authInfo (user/password)
            // from
            // the header
            authHeader = authHeader.trim();
            int blank = authHeader.indexOf(' ');
            if (blank > 0) {
                String authType = authHeader.substring(0, blank);
                String authInfo = authHeader.substring(blank).trim();

                // Check whether authorization type matches
                if (authType.equalsIgnoreCase(AUTHENTICATION_SCHEME_BASIC)) {
                    try {
                        String srcString = base64Decode(authInfo);
                        int i = srcString.indexOf(':');
                        String username = srcString.substring(0, i);
                        String password = srcString.substring(i + 1);

                        // authenticate
                        if (this.securityProvider.authenticate(username, password)) {
                            // as per the spec, set attributes
                            request.setAttribute(HttpContext.AUTHENTICATION_TYPE, "");
                            request.setAttribute(HttpContext.REMOTE_USER, username);

                            // succeed
                            return true;
                        }
                    }
                    catch (Exception e) {
                        // Ignore
                    }
                }
            }
        }

        // request authentication
        try {
            response.setHeader(HEADER_WWW_AUTHENTICATE, AUTHENTICATION_SCHEME_BASIC + " realm=\"" + this.realm + "\"");
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            response.setContentLength(0);
            response.flushBuffer();
        }
        catch (IOException ioe) {
            // failed sending the response ... cannot do anything about it
        }

        // inform HttpService that authentication failed
        return false;
    }

    public static String base64Decode(String srcString) {
        byte[] transformed = Base64.decodeBase64(srcString);
        try {
            return new String(transformed, "ISO-8859-1");
        }
        catch (UnsupportedEncodingException uee) {
            return new String(transformed);
        }
    }
}
